{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Bell state quantum circuit\n",
    "In this exercise we creates a two qubit circuit, with two qubits in superposition, and then measures the individual qubits, resulting in two coin toss results with the following possible outcomes with equal probability: $|00\\rangle$, $|01\\rangle$, $|10\\rangle$, and $|11\\rangle$. This is like tossing two coins.\n",
    "\n",
    "We then take a look at using the CX (Controlled-NOT) gate to entangle the two qubits in a so called Bell state. This surprisingly results in the following possible outcomes with equal probability: $|00\\rangle$ and $|11\\rangle$. Two entangled qubits do not at all behave like two tossed coins.\n",
    "\n",
    "We then run the circuit a large number of times to see what the statistical behavior of the qubits are.\n",
    "Finally, we run the circuit on real IBM Q hardware to see how real physical qubits behave.\n",
    "\n",
    "In this exercise we introduce the CX gate, which creates entanglement between two qubits, by flipping the controlled qubit (q_1) if the controlling qubit (q_0) is 1.\n",
    "```\n",
    "        ┌───┐     \n",
    "q_0: |0>┤ H ├──■──\n",
    "        └───┘┌─┴─┐\n",
    "q_1: |0>─────┤ X ├\n",
    "             └───┘\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Import the required libraries, including the IBM Q library for working with IBM Q hardware."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [],
   "source": [
    "from qiskit import QuantumCircuit, execute, Aer, IBMQ\n",
    "from qiskit.tools.monitor import job_monitor\n",
    "\n",
    "# Import visualization\n",
    "from qiskit.visualization import plot_histogram, plot_bloch_multivector\n",
    "\n",
    "# Add the state vector calculation function\n",
    "def get_psi(circuit): \n",
    "    global psi\n",
    "    backend = Aer.get_backend('statevector_simulator') \n",
    "    psi = execute(circuit, backend).result().get_statevector(circuit)\n",
    "    \n",
    "#Load account information \n",
    "IBMQ.load_account()\n",
    "provider = IBMQ.get_provider()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "How many qubits do we want to use:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "n_qubits=int(input(\"Enter number of qubits:\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Create quantum circuit that includes the quantum register and the classic register. Then add a Hadamard (super position) gate to all the qubits. Add measurement gates."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "qc1 = QuantumCircuit(n_qubits,n_qubits)\n",
    "\n",
    "for qubit in range (0,n_qubits):\n",
    "    qc1.h(qubit) #A Hadamard gate that creates a superposition\n",
    "    qc1.measure(qubit,qubit)\n",
    "\n",
    "print(qc1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Now run the circuit with one shot. This represents a two-coin coin toss. Like before, we can also run the circuit more times to get statistics on the possible outcomes.\n",
    " "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "backend = Aer.get_backend('qasm_simulator')\n",
    "\n",
    "job = execute(qc1, backend, shots=1)\n",
    "counts1  = job.result().get_counts(qc1)\n",
    "print(counts1)\n",
    "plot_histogram(counts1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Let's look at a similar simulation result in Bloch sphere format."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "# Display the Bloch spheres\n",
    "get_psi(qc1)\n",
    "print(psi)\n",
    "plot_bloch_multivector(psi)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Now we are going to do something different. We will entangle the qubits.\n",
    "\n",
    "Create quantum circuit that includes the quantum register and the classic register. Then add a Hadamard (super position) gate to the first qubit. Then add a controlled-NOT gate (cx) between the first and second qubit, entangling them. Add measurement gates. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "qc2 = QuantumCircuit(n_qubits,n_qubits)\n",
    "\n",
    "qc2.h(0) # A Hadamard gate that puts the first qubit in superposition\n",
    "for qubit in range (1,n_qubits):\n",
    "    qc2.cx(0,qubit) #A controlled NOT gate that entangles the qubits.\n",
    "    qc2.measure(qubit,qubit)\n",
    "\n",
    "qc2.measure(0,0)\n",
    "print(qc2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Set the backend to a local simulator. Then create a quantum job for the circuit, the selected backend, that runs just one shot to simulate a coin toss with two simultaneously tossed coins, then run the job. Display the result; either 0 for up (base) or 1 for down (excited) for each qubit. Display the result as a histogram. Either |00> or |11> with 100% probability."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "backend = Aer.get_backend('qasm_simulator')\n",
    "\n",
    "job = execute(qc2, backend, shots=1)\n",
    "counts2  = job.result().get_counts(qc2)\n",
    "print(counts2)\n",
    "plot_histogram(counts2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Let's look at a similar simulation result in Bloch sphere format."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "# Display the Bloch spheres\n",
    "get_psi(qc2)\n",
    "print(psi)\n",
    "plot_bloch_multivector(psi)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "Note how the two qubits completely agree. They are entangled. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Now, lets run quite a few more shots, and display the statistsics for the two results: $|00\\rangle$ and $|11\\rangle$. This time, as we are no longer just talking about two qubits, but the amassed results of thousands of runs on these qubits, we can no longer use the Block sphere as a display tool."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "job = execute(qc2, backend, shots=1000)\n",
    "result = job.result() \n",
    "counts  = result.get_counts()\n",
    "print(counts)\n",
    "plot_histogram(counts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "And look at that, we are back at our coin toss results, fifty-fifty. Every time one of the coins comes up heads (|0>) the other one follows suit. Tossing one coin we immediately know what the other one will come up as; the coins (qubits) are entangled."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "**Important:** With the simulator we get perfect results, only |00> or |11>. On a real NISQ (Noisy Intermediate Scale Quantum computer) we do not expect perfect results like this. Let's run the Bell state once more, but on an actual IBM Q quantum computer."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "Grab the least busy IBM Q backend."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [],
   "source": [
    "from qiskit.providers.ibmq import least_busy\n",
    "backend = least_busy(provider.backends(operational=True, simulator=False))\n",
    "#backend = provider.get_backend('ibmqx2')\n",
    "print(\"Selected backend:\",backend.status().backend_name)\n",
    "print(\"Number of qubits(n_qubits):\", backend.configuration().n_qubits)\n",
    "print(\"Pending jobs:\", backend.status().pending_jobs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Lets run a large number of shots, and display the statistsics for the two results: $|00\\rangle$ and $|11\\rangle$ on the real hardware. Monitor the job and display our place in the queue."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [],
   "source": [
    "if n_qubits > backend.configuration().n_qubits:\n",
    "    print(\"Your circuit contains too many qubits (\",n_qubits,\"). Start over!\")\n",
    "else:\n",
    "    job = execute(qc2, backend, shots=1000)\n",
    "    job_monitor(job)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Get the results, and display in a histogram. Notice how we no longer just get the perfect |00> and |11> entangled results, but also a few results that include non-entangled |01> and |10>. At this stage, quantum computers are not perfect calculating machines, but pretty noisy. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [],
   "source": [
    "result = job.result()\n",
    "counts  = result.get_counts(qc2)\n",
    "print(counts)\n",
    "plot_histogram(counts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "That was the simple readout. Let's take a look at the whole returned results:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [],
   "source": [
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "celltoolbar": "Slideshow",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
